package com.ian.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.concurrent.DelegatingSecurityContextExecutor;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;
import java.util.concurrent.Executor;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * Spring Security Config
 *
 * @author Witt
 * @version 1.0.0
 * @date 2022/4/30
 */

// @EnableWebSecurity: to enable Spring Security integration with Spring MVC
@EnableWebSecurity
/*
 * @EnableGlobalMethodSecurity 启用全局方法安全功能
 *   securedEnabled = true：启用注解 @Secured
 *   prePostEnabled = true：启用注解 @PreAuthorize(), @PostAuthorize(), @PreFilter(), @PostFilter()
 *   jsr250Enabled = true：启用JSR-250. These are standards-based and allow simple role-based constraints to be applied but do not have the power Spring Security’s native annotations.
 * note1: The annotated methods will only be secured for instances which are defined as Spring beans (in the same application context in which method-security is enabled). If you want to secure instances which are not created by Spring (using the new operator, for example) then you need to use AspectJ.
 * note2: You can enable more than one type of annotation in the same application, but only one type should be used for any interface or class as the behaviour will not be well-defined otherwise. If two annotations are found which apply to a particular method, then only one of them will be applied.
 */
// 启用注解 @PreAuthorize(), @PostAuthorize(), @PreFilter(), @PostFilter()
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
//@EnableMethodSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                .mvcMatchers("/resources/**", "/signup", "/about").permitAll()
//                .mvcMatchers("/admin/**").hasRole("ADMIN")
                .mvcMatchers("/db/**")
                // .not() // 效果就是权限属性前加上 “非” 符号，attribute = "!" + attribute; 即取反，使用和当前访问权限相反的权限
                // hasRole('ADMIN') 和 hasAuthority('ADMIN') 的唯一区别：hasRole('ADMIN')的参数 ADMIN 前面会自动补上前缀，默认是 ROLE_，然后再判断；
                //   而 hasAuthority('ADMIN') 直接判断；
                //   但是，hasPermission('ADMIN') 和上面的两者完全不同，需要自己写一些实现类来自定义判断逻辑
                .access("hasRole('ADMIN') and hasRole('DBA') and (hasAuthority('ADMIN') or isAnonymous() or isAuthenticated() or isFullyAuthenticated() or isRememberMe() or permitAll() or hasIpAddress('192.168.1.0/24') or @webSecurity.check(authentication,request))")
                // permitAll()和anonymous()的区别：anonymous() 只允许匿名用户访问,不允许已登录用户访问；permitAll() 不管登录，不登录，都能访问
                .antMatchers("/hello1").anonymous()
                .antMatchers("/hello2", "/invalidSessionStrategy.html",
                        "invalidSessionUrl.html", "/logout", "/login?logout").permitAll()
//                 .antMatchers("/**").permitAll()
                .antMatchers(HttpMethod.GET, "/user").hasRole("user")
                .antMatchers(HttpMethod.GET, "/admin/**").hasRole("admin")
                .anyRequest().authenticated()
                .and()
                .rememberMe()
                .and()
                // loginPage("/login")：映射到Controller中的方法 /login，再由该方法跳转到具体的 html页面
                .formLogin(form -> form.loginPage("/login").loginProcessingUrl("/doLogin").permitAll())
                .logout(logout -> logout.permitAll())
                .csrf().disable() // default: enabled
                // by default uses a Bean by the name of corsConfigurationSource
                .cors(withDefaults())
        ;
    }

    /**
     * In memory
     * @return UserDetailsService类型的对象
     */
    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
                User.builder()
                        .username("user")
                        // 密码明文是 123
                        .password("{bcrypt}$2a$10$EOWPKZDdcX5u1SYprJVDy.7puK7D/NVEEBdhutPCpJeicssrlLhJm")
                        .roles("user")
                        .build(),
                User.withUsername("admin")
                        // 密码明文是 123
                        .password("{bcrypt}$2a$10$EOWPKZDdcX5u1SYprJVDy.7puK7D/NVEEBdhutPCpJeicssrlLhJm")
                        .roles("admin")
                        .build());
    }

    @Bean
    public Executor executor() {
        SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
        UsernamePasswordAuthenticationToken authentication
                = new UsernamePasswordAuthenticationToken("user2", "123", AuthorityUtils.createAuthorityList("ROLE_admin2"));
//        System.out.println("authentication=" + authentication);
//        Object principal = authentication.getPrincipal();
//        System.out.println("principal=" + principal);

        emptyContext.setAuthentication(authentication);

        SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();

        // DelegatingSecurityContextExecutor 使用了 装饰者模式，装饰了 原始的 SimpleAsyncTaskExecutor 对象 delegateExecutor
        DelegatingSecurityContextExecutor wrappedExecutor = new DelegatingSecurityContextExecutor(delegateExecutor, emptyContext);

        /**
         * What if we wanted to use the user from SecurityContextHolder at the time we invoked
         * executor.execute(Runnable) (i.e. the currently logged in user) to process originalRunnable?
         * This can be done by removing the SecurityContext argument from our DelegatingSecurityContextExecutor constructor.
         */
        DelegatingSecurityContextExecutor wrappedExecutorWithCurrentlyLoggedInUser = new DelegatingSecurityContextExecutor(delegateExecutor);

        return wrappedExecutor;
//        return wrappedExecutorWithCurrentlyLoggedInUser;
    }

    /**
     * 添加了该 bean 后报错：java.lang.IllegalStateException: No ServletContext set
     * SOLUTION: 将该 bean 单独放到一个配置类中
     * @return
    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
//        messageSource.setBasename("classpath:org/springframework/security/messages/");
//        messageSource.addBasenames("classpath:org/springframework/security/messages/");
         messageSource.addBasenames("classpath:org/springframework/security/messages/messages_zh_CN");
        return messageSource;
    }
     */

    /**
     * to support Spring Data Integration
     *
     * @return SecurityEvaluationContextExtension
     */
    @Bean
    SecurityEvaluationContextExtension securityEvaluationContextExtension() {
        return new SecurityEvaluationContextExtension();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedOrigins(Arrays.asList("https://example.com"));
        corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }
}
